package io.gitee.hejiang.jersey;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.fileupload.MultipartStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
import org.glassfish.jersey.process.Inflector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;

import com.fasterxml.jackson.databind.JavaType;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;

import io.gitee.hejiang.action.SwaggerSqlAction;
import io.gitee.hejiang.action.SwaggerSqlActionProvider;
import io.gitee.hejiang.action.impl.KeycloakSecurityAction;
import io.swagger.inflector.config.Configuration;
import io.swagger.inflector.converters.ConversionException;
import io.swagger.inflector.converters.DefaultConverter;
import io.swagger.inflector.converters.InputConverter;
import io.swagger.inflector.examples.ExampleBuilder;
import io.swagger.inflector.examples.models.ArrayExample;
import io.swagger.inflector.examples.models.Example;
import io.swagger.inflector.examples.models.ObjectExample;
import io.swagger.inflector.models.ApiError;
import io.swagger.inflector.models.RequestContext;
import io.swagger.inflector.models.ResponseContext;
import io.swagger.inflector.processors.EntityProcessor;
import io.swagger.inflector.processors.EntityProcessorFactory;
import io.swagger.inflector.schema.SchemaValidator;
import io.swagger.inflector.utils.ApiException;
import io.swagger.inflector.validators.ValidationException;
import io.swagger.inflector.validators.ValidationMessage;
import io.swagger.models.Model;
import io.swagger.models.Operation;
import io.swagger.models.Swagger;
import io.swagger.models.auth.SecuritySchemeDefinition;
import io.swagger.models.parameters.BodyParameter;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.parameters.QueryParameter;
import io.swagger.models.parameters.SerializableParameter;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.StringProperty;
import io.swagger.util.Json;

public class JerseyOperationInflector extends JerseyReflectionUtils
		implements Inflector<ContainerRequestContext, Object> {
	private static final Logger LOGGER = LoggerFactory.getLogger(JerseyOperationInflector.class);

	private static Set<String> commonHeaders = new HashSet<String>();

	static {
		commonHeaders.add("Host");
		commonHeaders.add("User-Agent");
		commonHeaders.add("Accept");
		commonHeaders.add("Content-Type");
		commonHeaders.add("Content-Length");
	}

	private String path;
	private String httpMethod;
	private Operation operation;
	private JavaType[] parameterTypes = null;
	private Map<String, Model> definitions;
	private InputConverter validator;

	@Inject
	private Provider<HttpServletRequest> requestProvider;
	@Inject
	private Provider<HttpServletResponse> responseProvider;

	private DataSource _datasource;
	private List<SwaggerSqlAction> _actions;
	private Map<String, JavaType> _argumentTypes;
	private Swagger _swagger;
	private boolean _keycloakEnabled;
	private File _uploadFolder;

	public JerseyOperationInflector(JerseyResourceConfig config, String path, String httpMethod, Operation operation,
			Swagger swagger, DataSource datasource) {
		this.setConfiguration(config.getInflectorConfiguration());
		this._uploadFolder = new File(config.getUploadFilePath());

		this.path = path;
		this.httpMethod = httpMethod;
		this.operation = operation;
		this.definitions = swagger.getDefinitions();
		this.validator = InputConverter.getInstance();
		this._swagger = swagger;
		this._datasource = datasource;
		super.setClassLoader(config.getCustomClassLoader());

		// 读取 keycloak.enabled 属性
		try {
			Environment env = config.getApplicationContext().getBean(Environment.class);
			_keycloakEnabled = env.getProperty("keycloak.enabled", Boolean.class, false);
		} catch (Exception ex) {
			_keycloakEnabled = false;
			LOGGER.error("Fail to read value of property keycloak.enabled");
		}

		detectMethod(operation);
	}

	@SuppressWarnings("unchecked")
	public void detectMethod(Operation operation) {
		Map<String, JavaType> output = new HashMap<String, JavaType>();

		// 返回的是参数类型列表, 第一个是返回值类型
		JavaType[] args = getOperationParameterClasses(operation, this.definitions);

		StringBuilder builder = new StringBuilder("public ResponseContext ");
		builder.append(getMethodName(path, httpMethod, operation)).append("(");
		for (int i = 1; i < args.length; i++) {
			String name = operation.getParameters().get(i - 1).getName();
			output.put(name, args[i]);

			if (i > 1) {
				builder.append(", ");
			}
			if (args[i] == null) {
				LOGGER.error("didn't expect a null class for {}", name);
				builder.append("??? ").append(name);
			} else if (args[i].getRawClass() != null) {
				String className = args[i].getRawClass().getSimpleName();
				if (className.startsWith("java.lang.")) {
					className = className.substring("java.lang.".length());
				}
				builder.append(className);

				int containedTypeIndex = 0;
				for (; containedTypeIndex < args[i].containedTypeCount(); ++containedTypeIndex) {
					builder.append(containedTypeIndex == 0 ? "<" : ", ")
							.append(args[i].containedType(containedTypeIndex).getRawClass().getSimpleName());
				}
				if (containedTypeIndex > 0) {
					builder.append(">");
				}

				builder.append(" ").append(name);
			}
		}
		builder.append(")");

		System.out.println(
				String.format("mapping `%6s %s`\n     to `%s`", this.httpMethod, this.path, builder.toString()));

		this.parameterTypes = args;
		this._argumentTypes = ImmutableMap.copyOf(output);
		try {
			List<Map<String, Object>> actionConfig = (List<Map<String, Object>>) operation.getVendorExtensions()
					.get("x-actions");
			this._actions = buildActions(actionConfig, operation.getSecurity(), _swagger.getSecurityDefinitions());
		} catch (Exception e) {
			LOGGER.error(e.getMessage());
		}
	}

	@Override
	public Object apply(ContainerRequestContext ctx) {
		LOGGER.info(ctx.getUriInfo().getRequestUri().toString());
		final RequestContext request = createContext(ctx);

		try {
			Map<String, Object> arguments = buildArguments(request);
			if (_actions != null && _actions.size() > 0) {
				Object output = executeActions(request, arguments);
				ResponseContext resp = new ResponseContext().entity(output);
				setContentType(request, resp, operation);
				ResponseBuilder builder = Response.ok().entity(output);
				if (resp.getContentType() != null) {
					builder.type(resp.getContentType());
				}
				return builder.build();
			} else {
				return constructSampleResponse(request);
			}
		} catch (IOException e) {
			throw new RuntimeException(e.getLocalizedMessage(), e);
		}
	}

	/**
	 * @param requestContext
	 * @return
	 */
	@SuppressWarnings("deprecation")
	protected Response constructSampleResponse(final RequestContext requestContext) {
		Map<String, io.swagger.models.Response> responses = operation.getResponses();
		if (responses != null) {
			String[] keys = new String[responses.keySet().size()];
			Arrays.sort(responses.keySet().toArray(keys));
			int code = 0;
			String defaultKey = null;
			for (String key : keys) {
				if (key.startsWith("2")) {
					defaultKey = key;
					code = Integer.parseInt(key);
					break;
				}
				if ("default".equals(key)) {
					defaultKey = key;
					code = 200;
					break;
				}
				if (key.startsWith("3")) {
					// we use the 3xx responses as defaults
					defaultKey = key;
					code = Integer.parseInt(key);
				}
			}

			if (defaultKey != null) {
				ResponseBuilder builder = Response.status(code);
				io.swagger.models.Response response = responses.get(defaultKey);

				if (response.getHeaders() != null && response.getHeaders().size() > 0) {
					for (String key : response.getHeaders().keySet()) {
						Property headerProperty = response.getHeaders().get(key);
						Object output = ExampleBuilder.fromProperty(headerProperty, definitions);
						if (output instanceof ArrayExample) {
							output = ((ArrayExample) output).asString();
						} else if (output instanceof ObjectExample) {
							LOGGER.debug(
									"not serializing output example, only primitives or arrays of primitives are supported");
						} else {
							output = ((Example) output).asString();
						}
						builder.header(key, output);
					}
				}

				Map<String, Object> examples = response.getExamples();
				if (examples != null) {
					for (MediaType mediaType : requestContext.getAcceptableMediaTypes()) {
						for (String key : examples.keySet()) {
							if (MediaType.valueOf(key).isCompatible(mediaType)) {
								builder.entity(examples.get(key)).type(mediaType);

								return builder.build();
							}
						}
					}
				}

				Object output = ExampleBuilder.fromProperty(response.getSchema(), definitions);

				if (output != null) {
					ResponseContext resp = new ResponseContext().entity(output);
					setContentType(requestContext, resp, operation);
					builder.entity(output);
					if (resp.getContentType() != null) {
						// this comes from the operation itself
						builder.type(resp.getContentType());
					} else {
						// get acceptable content types
						List<EntityProcessor> processors = EntityProcessorFactory.getProcessors();

						MediaType responseMediaType = null;

						// take first compatible one
						for (EntityProcessor processor : processors) {
							if (responseMediaType != null) {
								break;
							}
							for (MediaType mt : requestContext.getAcceptableMediaTypes()) {
								LOGGER.debug("checking type " + mt.toString() + " against "
										+ processor.getClass().getName());
								if (processor.supports(mt)) {
									builder.type(mt);
									responseMediaType = mt;
									break;
								}
							}
						}

						if (responseMediaType == null) {
							// no match based on Accept header, use first
							// processor in list
							for (EntityProcessor processor : processors) {
								List<MediaType> supportedTypes = processor.getSupportedMediaTypes();
								if (supportedTypes.size() > 0) {
									builder.type(supportedTypes.get(0));
									break;
								}
							}
						}
					}

					builder.entity(output);
				}
				return builder.build();
			} else {
				LOGGER.debug("no response type to map to, assume 200");
				code = 200;
			}
			return Response.status(code).build();
		}
		return Response.ok().build();
	}

	protected Object executeActions(RequestContext request, Map<String, Object> arguments) {
		HashMap<String, Object> context = new HashMap<String, Object>();
		Object result = null;
		
		try (Connection connection = _datasource.getConnection();) {
			final boolean supportsTransactions = connection.getMetaData().supportsTransactions();
			if (supportsTransactions) {
			    connection.setAutoCommit(false);
			}
			// 链式执行
			try {
				result = _actions.get(0).execute(request, connection, arguments, context);
				if (supportsTransactions) {
				    connection.commit();
				}
			} catch (Exception e) {
				LOGGER.error(e.getLocalizedMessage());
				if (supportsTransactions) {
					connection.rollback();
				}
				// wrap exception and throw
				if (e instanceof RuntimeException) {
					throw (RuntimeException) e;
				} else if (e instanceof SQLException) {
					throw (SQLException) e;
				} else {
					throw new RuntimeException("Fail to execute", e);
				}
			}
		} catch (SQLException e) {
			throw new RuntimeException("Fail to execute", e);
		}

		return result;
	}

	// 构建请求参数对象列表
	protected Map<String, Object> buildArguments(RequestContext request) throws IOException {
		Map<String, Object> output = new HashMap<>();
		List<Parameter> parameters = operation.getParameters();
		if (parameters == null) {
			return output;
		}

		ContainerRequestContext containerContext = request.getContext();

		UriInfo uri = containerContext.getUriInfo();
		Set<String> existingKeys = new HashSet<String>();

		// 收集查询参数 keys
		for (Iterator<String> x = uri.getQueryParameters().keySet().iterator(); x.hasNext();) {
			existingKeys.add(x.next() + ": qp");
		}

		// 收集路径参数 keys
		for (Iterator<String> x = uri.getPathParameters().keySet().iterator(); x.hasNext();) {
			existingKeys.add(x.next() + ": pp");
		}

		// 收集 http 头 keys。暂不支持通过 http 头传递参数
		// for (Iterator<String> x = containerContext.getHeaders().keySet().iterator();
		// x
		// .hasNext();) {
		// String key = x.next();
		// if(!commonHeaders.contains(key))
		// existingKeys.add(key);
		// }

		MultivaluedStringMap formDataMap = null;

		MediaType mediaType = request.getMediaType();
		if (mediaType != null && mediaType.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE)) {
			// 解析 multipart/form-data 数据
			// boundary 是 multipart/form-data 数据的分界
			// 注意实体流数据只能读取一次
			String boundary = mediaType.getParameters().get("boundary");
			if (boundary != null) {
				formDataMap = parseMultipartFormData(containerContext.getEntityStream(), boundary);
			}
		} else if (mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE)) {
			// 解析 application/x-www-form-urlencoded 数据
			String formDataString = IOUtils.toString(containerContext.getEntityStream(), "UTF-8");
			formDataMap = new MultivaluedStringMap();
			for (String part : formDataString.split("&")) {
				String[] kv = part.split("=");
				if (kv.length == 2) {
					String key = kv[0];
					String value = URLDecoder.decode(kv[1], "utf-8");
					formDataMap.add(key, value);
				}

				existingKeys.add(kv[0] + ": fp");
			}
		}

		List<ValidationMessage> missingParams = new ArrayList<ValidationMessage>();

		// 逐个构建传入的请求参数对象
		for (int i = 0; i < parameters.size();) {
			Parameter parameter = parameters.get(i);
			JavaType parameterType = parameterTypes[++i];
			String in = parameter.getIn();
			Object obj = null;
			try {
				if ("formData".equals(in)) {
					obj = buildFormDataArgument(parameter, parameterType, mediaType, formDataMap, existingKeys,
							missingParams);
				} else {
					obj = buildNonFormDataArgument(parameter, parameterType, containerContext, uri, in, existingKeys,
							missingParams);
				}
			} catch (NumberFormatException e) {
				LOGGER.error("Couldn't find " + parameter.getName() + " (" + in + ") to " + parameterType, e);
			}

			output.put(parameter.getName(), obj);
		}

		// 请求中带有，但是没有用到的 keys
		if (existingKeys.size() > 0) {
			LOGGER.debug("unexpected keys: " + existingKeys);
		}

		// 必须传入但是没有传入的参数
		if (missingParams.size() > 0) {
			StringBuilder builder = new StringBuilder();
			builder.append("Input error");
			if (missingParams.size() > 1) {
				builder.append("s");
			}
			builder.append(": ");
			int count = 0;
			for (ValidationMessage message : missingParams) {
				if (count > 0) {
					builder.append(", ");
				}
				if (message != null && message.getMessage() != null) {
					builder.append(message.getMessage());
				} else {
					builder.append("no additional input");
				}
				count += 1;
			}
			int statusCode = config.getInvalidRequestStatusCode();
			ApiError error = new ApiError().code(statusCode).message(builder.toString());
			throw new ApiException(error);
		}

		// 返回请求参数集合
		return output;
	}

	protected Object buildNonFormDataArgument(Parameter parameter, JavaType parameterClass, ContainerRequestContext ctx,
			UriInfo uri, String in, Set<String> existingKeys, List<ValidationMessage> missingParams) {
		Object obj = null;
		try {
			String paramName = parameter.getName();
			Class<?> cls = parameterClass.getRawClass();
			if ("body".equals(in)) {
				if (ctx.hasEntity()) {
					BodyParameter body = (BodyParameter) parameter;
					obj = EntityProcessorFactory.readValue(ctx.getMediaType(), ctx.getEntityStream(), cls);
					if (obj != null) {
						validate(obj, body.getSchema(), SchemaValidator.Direction.INPUT);
					}
				} else if (parameter.getRequired()) {
					ValidationException e = new ValidationException();
					e.message(new ValidationMessage().message("The input body `" + paramName + "` is required"));
					throw e;
				}
			} else if ("query".equals(in)) {
				existingKeys.remove(paramName + ": qp");
				obj = validator.convertAndValidate(uri.getQueryParameters().get(parameter.getName()), parameter, cls,
						definitions);
			} else if ("path".equals(in)) {
				existingKeys.remove(paramName + ": pp");
				obj = validator.convertAndValidate(uri.getPathParameters().get(parameter.getName()), parameter, cls,
						definitions);
			} else if ("header".equals(in)) {
				obj = validator.convertAndValidate(ctx.getHeaders().get(parameter.getName()), parameter, cls,
						definitions);
			}
		} catch (ConversionException e) {
			missingParams.add(e.getError());
		} catch (ValidationException e) {
			missingParams.add(e.getValidationMessage());
		}
		return obj;
	}

	protected Object buildFormDataArgument(Parameter parameter, JavaType parameterType, MediaType mediaType,
			MultivaluedStringMap formDataMap, Set<String> existingKeys, List<ValidationMessage> missingParams) {
		Object obj = null;

		String parameterName = parameter.getName();
		existingKeys.remove(parameterName + ": fp");

		List<String> vals = formDataMap.get(parameterName);

		Class<?> cls = parameterType.getRawClass();
		// TODO - 暂时使用文件路径代替文件
		if (cls == File.class) {
			cls = String.class;
		} else if (cls == List.class) {
            SerializableParameter sp = (SerializableParameter) parameter;
            if (sp.getItems() != null) {
                Property inner = sp.getItems();
                Parameter innerParam = new QueryParameter().property(inner);
				JavaType innerClass = new DefaultConverter().getTypeFromParameter(innerParam, definitions);
				if (innerClass.getRawClass() == File.class) {
					sp.setCollectionFormat("multi");
					sp.setItems(new StringProperty());
				}
            }
		}
		// 进行格式转换
		try {
			obj = validator.convertAndValidate(vals, parameter, cls, definitions);
		} catch (ConversionException e) {
			missingParams.add(e.getError());
		} catch (ValidationException e) {
			missingParams.add(e.getValidationMessage());
		}

		return obj;
	}

	/**
	 * multipart/form-data 协议格式和内容参考
	 * https://blog.csdn.net/qq_33706382/article/details/78168325
	 * 
	 * @throws IOException
	 */
	@SuppressWarnings("deprecation")
	protected MultivaluedStringMap parseMultipartFormData(InputStream formData, String boundary) throws IOException {
		MultivaluedStringMap formDataMap = new MultivaluedStringMap();

		MultipartStream multipartStream = new MultipartStream(formData, boundary.getBytes());
		boolean hasNextPart = multipartStream.skipPreamble();
		while (hasNextPart) {
			String headersString = multipartStream.readHeaders();
			if (headersString == null) {
				// 没有头信息的数据，暂时丢弃
				LOGGER.error("由于缺少 header 信息, 该部分 multipart/form-data 数据被丢弃");

				hasNextPart = multipartStream.readBoundary();
				continue;
			}

			// 解析头信息, 每个 CSVRecord 是一行, 行内字段使用 ';' 拆分
			Map<String, String> headers = new HashMap<String, String>();

			for (CSVRecord record : CSVFormat.DEFAULT.withDelimiter(';').parse(new StringReader(headersString))) {
				// 取出记录的每个字段
				for (String kvString : record) {
					Iterable<CSVRecord> kvRecords = CSVFormat.DEFAULT.withDelimiter('=')
							.parse(new StringReader(kvString)); // 显然这里返回的集合中只有一条记录
					// 取出记录
					CSVRecord kvRecord = kvRecords.iterator().next();
					// 如果有两个字段, 也就是 key=value 形式
					if (kvRecord.size() == 2) {
						String key = kvRecord.get(0).trim();
						String value = kvRecord.get(1).trim();
						headers.put(key, value);
						continue;
					}

					// 不是两个字段，那就用 ':' 拆分
					kvRecords = CSVFormat.DEFAULT.withDelimiter(':').parse(new StringReader(kvString)); // 显然这里返回的集合中只有一条记录
					// 取出记录
					kvRecord = kvRecords.iterator().next();
					// 如果有两个字段, 也就是 key: value 形式
					if (kvRecord.size() == 2) {
						String key = kvRecord.get(0).trim();
						String value = kvRecord.get(1).trim();
						headers.put(key, value);
						continue;
					}

					// 应该不会有这种情况
					LOGGER.error("无效的 multipart/form-data 头记录 {}", kvString);
				}
			}

			// 查找一个名为 name 的头
			if (!headers.containsKey("name")) {
				// 应该不会有这种情况
				LOGGER.error("无效的 multipart/form-data 头信息 {}", headersString);
				continue;
			}

			String name = headers.get("name");

			// 从头中获取 filename 信息
			String filename = extractFilenameFromHeaders(headers);
			if (filename != null) {
				try {
					// 此时上传了一个文件
					File file = File.createTempFile(Files.getNameWithoutExtension(filename) + "-",
							"." + Files.getFileExtension(filename), this._uploadFolder);
					FileOutputStream fo = new FileOutputStream(file);
					multipartStream.readBodyData(fo);
					fo.close();
					formDataMap.add(name, file.getPath());
				} catch (Exception e) {
					LOGGER.error("Failed to extract uploaded file", e);
				}
			} else {
				// 这种情况实际上是一个 value
				ByteArrayOutputStream bo = new ByteArrayOutputStream();
				multipartStream.readBodyData(bo);
				String value = bo.toString();
				formDataMap.add(name, value);
			}

			// 读取 multipart/form-data 的下一部分
			hasNextPart = multipartStream.readBoundary();
		}

		return formDataMap;
	}

	static String extractFilenameFromHeaders(Map<String, String> headers) {
		String filename = headers.get("filename");
		if (StringUtils.isBlank(filename)) {
			return null;
		}

		filename = filename.trim();

		int ix = filename.lastIndexOf(File.separatorChar);
		if (ix != -1) {
			filename = filename.substring(ix + 1).trim();
			if (StringUtils.isBlank(filename)) {
				return null;
			}
		}

		return filename;
	}

	public void validate(Object o, Property property, SchemaValidator.Direction direction) throws ApiException {
		doValidation(o, property, direction);
	}

	public void validate(Object o, Model model, SchemaValidator.Direction direction) throws ApiException {
		doValidation(o, model, direction);
	}

	public void setContentType(RequestContext res, ResponseContext resp, Operation operation) {
		// honor what has been set, it may be determined by business logic in
		// the controller
		if (resp.getContentType() != null) {
			return;
		}
		List<String> available = operation.getProduces();
		if (available != null) {
			for (String a : available) {
				MediaType mt = MediaType.valueOf(a);
				for (MediaType acceptable : res.getAcceptableMediaTypes()) {
					if (mt.isCompatible(acceptable)) {
						resp.setContentType(mt);
						return;
					}
				}
			}
			if (available.size() > 0) {
				resp.setContentType(MediaType.valueOf(available.get(0)));
				return;
			}
		}

		// 设置默认 Content-type
		resp.setContentType(new MediaType("application", "json", "utf-8"));
	}

	// package protected to facilitate unit testing
	RequestContext createContext(ContainerRequestContext from) {
		HttpServletRequest request = getHttpServletRequest();
		HttpServletResponse response = getHttpServletResponse();
		return new RequestContext(from, request, response);
	}

	private HttpServletRequest getHttpServletRequest() {
		if (requestProvider != null) {
			return requestProvider.get();
		} else {
			LOGGER.warn("HttpServletRequest provider was null - returning null request!");
			return null;
		}
	}

	private HttpServletResponse getHttpServletResponse() {
		if (responseProvider != null) {
			return responseProvider.get();
		} else {
			LOGGER.warn("HttpServletResponse provider was null - returning null response!");
			return null;
		}
	}

	private void doValidation(Object value, Object schema, SchemaValidator.Direction direction) throws ApiException {
		if (config.getValidatePayloads().isEmpty()) {
			return;
		}
		switch (direction) {
		case INPUT:
			if (config.getValidatePayloads().contains(Configuration.Direction.IN)
					&& !SchemaValidator.validate(value, Json.pretty(schema), direction)) {
				throw new ApiException(new ApiError().code(config.getInvalidRequestStatusCode())
						.message("Input does not match the expected structure"));
			}
			break;
		case OUTPUT:
			if (config.getValidatePayloads().contains(Configuration.Direction.OUT)
					&& !SchemaValidator.validate(value, Json.pretty(schema), direction)) {
				throw new ApiException(new ApiError().code(config.getInvalidRequestStatusCode())
						.message("The server generated an invalid response"));
			}
			break;
		}
	}

	private List<SwaggerSqlAction> buildActions(List<Map<String, Object>> actionConfig,
			List<Map<String, List<String>>> security, Map<String, SecuritySchemeDefinition> securitySchemes)
			throws Exception {
		List<SwaggerSqlAction> actions = new ArrayList<SwaggerSqlAction>();

		SwaggerSqlAction lastAction = new KeycloakSecurityAction(security, securitySchemes, _keycloakEnabled);
		Map<String, Object> lastConfig = null;
		actions.add(lastAction);

		if (actionConfig != null) {
			for (Map<String, Object> config : actionConfig) {
				SwaggerSqlAction action = SwaggerSqlActionProvider.build(config);
				actions.add(action);

				lastAction.init(lastConfig, _argumentTypes, action);

				lastAction = action;
				lastConfig = config;

			}
		}

		lastAction.init(lastConfig, _argumentTypes, null);

		return actions;
	}
}